package com.apes.framework.plugin.fsm.core;

import com.apes.framework.plugin.fsm.FsmState;
import org.squirrelframework.foundation.fsm.*;

import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

public class ApesDotVisitor implements DotVisitor {

    protected final StringBuilder buffer = new StringBuilder();

    protected void writeLine(final String msg) {
        buffer.append(msg).append("\n");
    }

    protected String quoteName(final String id) {
        return "\"" + id + "\"";
    }

    protected void saveFile(final String filename, String content) {
        try {
            FileWriter file = new FileWriter(filename);
            file.write(content);
            file.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * get enum name instead of toString value
     *
     * @param enumObj
     * @return
     */
    protected static String getName(Object enumObj) {
        String stateValue;
        if (enumObj.getClass().isEnum()) {
            try {
                stateValue = (String) enumObj.getClass().getMethod("name").invoke(enumObj, null);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        } else {
            stateValue = enumObj.toString();
        }
        return stateValue;
    }

    /**
     * quote with enum name value
     *
     * @param enumObj
     * @return
     */
    protected String quoteEnumName(final Object enumObj) {
        return quoteName(getName(enumObj));
    }


    protected final StringBuilder transBuf = new StringBuilder();

    @Override
    public void visitOnEntry(StateMachine<?, ?, ?, ?> visitable) {
        writeLine("digraph {\ncompound=true;");
        writeLine("subgraph cluster_StateMachine {\nlabel=\"" + visitable.toString() + "\";");
    }

    @Override
    public void visitOnExit(StateMachine<?, ?, ?, ?> visitable) {
        buffer.append(transBuf);
        writeLine("}}");
    }

    @Override
    public void visitOnEntry(ImmutableState<?, ?, ?, ?> visitable) {
        String stateId = getName(visitable.getStateId());
        String stateLabel = visitable.getStateId().toString();
        if (visitable.hasChildStates()) {
            writeLine("subgraph cluster_" + stateId + " {\nlabel=\"" + stateLabel + "\";");
            if (visitable.getHistoryType() == HistoryType.DEEP) {
                writeLine(stateId + "History" + " [label=\"\"];");
            } else if (visitable.getHistoryType() == HistoryType.SHALLOW) {
                writeLine(stateId + "History" + " [label=\"\"];");
            }
        } else {
            String style = "";
            if (((FsmState) visitable.getStateId()).isStarted()) {
                style = " style = \"filled\" shape=\"Mrecord\" color = \"red\" width=.1  height=.1";
            } else if (visitable.isFinalState()) {
                style = " style = \"filled\" shape=\"doublecircle\" color = \"yellow\" width=.7";
            }
            writeLine(stateId + " [label=\"" + stateLabel + "\"" + style + "];");
        }
    }

    @Override
    public void visitOnExit(ImmutableState<?, ?, ?, ?> visitable) {
        if (visitable.hasChildStates()) {
            writeLine("}");
        }
    }

    @Override
    public void visitOnEntry(ImmutableTransition<?, ?, ?, ?> visitable) {
        ImmutableState<?, ?, ?, ?> sourceState = visitable.getSourceState();
        ImmutableState<?, ?, ?, ?> targetState = visitable.getTargetState();
        String sourceStateId = getName(sourceState.getStateId());
        String targetStateId = getName(targetState.getStateId());
        boolean sourceIsCluster = sourceState.hasChildStates();
        boolean targetIsCluster = targetState.hasChildStates();
        String source = (sourceIsCluster) ? "cluster_" + sourceStateId : null;
        String target = (targetIsCluster) ? "cluster_" + targetStateId : null;
        String realStart = (sourceIsCluster) ? getName(getSimpleChildOf(sourceState).getStateId()) : sourceStateId;
        String realEnd = (targetIsCluster) ? getName(getSimpleChildOf(targetState).getStateId()) : targetStateId;
        String edgeLabel = visitable.getEvent().toString();
        String ltail = (source != null) ? "ltail=\"" + source + "\"" : null;
        String lhead = (target != null) ? "lhead=\"" + target + "\"" : null;
        transBuf.append(
                "\n" + realStart + " -> " + realEnd + " [" + ((ltail != null) ? ltail + "," : "") + ((lhead != null) ? lhead
                        + "," : "") + " label=\"" + edgeLabel + "\"];");
    }

    public ImmutableState<?, ?, ?, ?> getSimpleChildOf(ImmutableState<?, ?, ?, ?> sourceState) {
        Queue<ImmutableState<?, ?, ?, ?>> list = new LinkedList<ImmutableState<?, ?, ?, ?>>();
        list.add(sourceState);
        while (!list.isEmpty()) {
            ImmutableState<?, ?, ?, ?> x = list.poll();
            int l = x.getChildStates().size();
            for (int i = 0; i < l; i++) {
                ImmutableState<?, ?, ?, ?> c = x.getChildStates().get(i);
                if (c.hasChildStates()) {
                    list.add(c);
                } else {
                    return c;
                }
            }
        }
        return sourceState;
    }

    @Override
    public void visitOnExit(ImmutableTransition<?, ?, ?, ?> visitable) {
    }

    @Override
    public void convertDotFile(String filename) {
        saveFile(filename + ".dot", buffer.toString());
    }

    @Override
    public String toString() {
        return buffer.toString();
    }
}
